Domains

Domains overview

Domain is the primary storage access point. It is responsible for:

  • Building and maintaining storage-wide information, such as persistent model (like reflection in .NET, but specialized for persistent types), database model (tables, indexes and so on), mappings between them CLR types & properties and database entities (tables, columns, etc).

  • Validating and upgrading the database schema.

  • Creating Session objects, you can consider it is a Session factory as well.

    Note

    Domain objects can be used concurrently, as well as any other read-only content available from it (e.g. Domain.Model or Domain.ExtractedSchema).

To create a domain, you must create DomainConfiguration first. And there are several ways of doing this:

  1. You can create it manually in code
  2. Load it from App.config section
  3. Finally, you can load it from App.config and modify it

In our example we’ll use the 3rd way:

// Loading the configiration from .config file section of certain assembly
System.Configuration.Configuration configuration = GetConfiguration();
var config = DomainConfiguration.Load(configuration, "Default");
// Modifying it by registering all the types from specified assembly
config.Types.Register(typeof(MyEntity).Assembly);
// And finally building the domain
var domain = await Domain.BuildAsync(config);

Now we have the Domain object. But to start working with persistent entities, we need a Session instance.

Configuring domain

To configure and build a Domain, you can use DomainConfiguration class. It contains a set of options that could be used to customize DataObjects.Net domain-level behavior.

Connection to database

DataObjects.Net supports two alternative ways of providing connection information:

  • Using connection string name syntax. It allows to specify standard connection string for underlying ADO.NET provider. Using connection string is the recommended approach.
  • Using connection URL syntax. Its main advantage is unification (the same syntax for all supported databases). Connection URLs are similar to those used in DataObjects.Net 3.x.

Connection string syntax

For specifying connection information DataObjects.Net provides support for standard ADO.NET connection string syntax. You can pass provider name and connection string to DomainConfiguration constructor:

var config = new DomainConfiguration("sqlserver",
  "Data Source=localhost; Initial Catalog=Tests; " +
  "Integrated Security=True; MultipleActiveResultSets=true;");

Possible provider names are sqlserver (used for connecting to Windows Asure Database as well), oracle, postgresql, mysql, firebird and sqlite. Their canonical names can be found in Xtensive.Orm.WellKnown.Provider class.

You can use connection strings in Domain configuration sections of App.config as well:

<domain
  name="Default"
  provider="sqlserver"
  connectionString="Data Source=localhost; Initial Catalog=MyDatabase;Integrated Security=True;"
  upgradeMode="Recreate" />

We recommend to use MultipleActiveResultTests parameter in connection string. Since DataObjects.Net supports on-demand materialization, this option allows it to deal with very large result sets. When this option is turned off, DO4 loads every result set completely. If connection URL is used instead of connection string, it is automatically added to underlying connection string.

ConnectionStrings.com provides many connection string examples for various ADO.NET providers.

Connection URL syntax

Connection URL can be passed to DomainConfiguration constructor or by assigning appropriate value to ConnectionUrl property:

var domainConfig = new DomainConfiguration("sqlserver://localhost/MyDatabase");

Alternatively you can specify connection URL in App.config section:

<configuration>
  <configSections>
    <section name="Xtensive.Orm" type="Xtensive.Orm.Configuration.Elements.ConfigurationSection, Xtensive.Orm" />
  </configSections>
  <Xtensive.Orm>
    <domains>
      <domain name="Default" connectionUrl="sqlserver://localhost/MyDatabase" />
    </domains>
  </Xtensive.Orm>
</configuration>

Any connection URL has the following structure:

protocol://user:password@server/database?parameter=value&parameter=value

Protocol is identical to provider name when using connection string. See previous section for details. Some parts of connection URL are optional, actual values and parameters may vary with each RDBMS. Any additional parameters are passed to underlying ADO.NET provider as is.

Microsoft SQL Server

  • sqlserver://localhost/Tests — connecting to database ‘Tests’ on a local machine with Windows authentication.
  • sqlserver://localhost\SQLExpress/WebSite — connecting to database ‘WebSite’ on a local machine with Windows authentication and non-default instance name ‘SQLExpress’.
  • sqlserver://iddqd:idfka@dbserver/Sales — connecting to database ‘Sales’ on a remote machine ‘dbserver’ with SQL Server authentication.

Azure SQL Database

  • sqlserver://login:password@server.database.windows.net/database — actual connection parameters could be taken from admin panel

PostgreSQL

  • postgresql://user:password@dbserver/tests — connecting to database ‘tests’ on server ‘dbserver’ as specified ‘user’ with specified ‘password’.
  • postgresql://user:password@dbserver/tests?Pooling=on&MinPoolSize=1&MaxPoolSize=5 — connecting to database ‘tests’ on server ‘dbserver’ as specified ‘user’ with specified ‘password’. Connection pooling is enabled.

Oracle

  • oracle://user:password@dbserver/tests – — connecting to database with service name ‘TESTS’ on server ‘dbserver’ as specified ‘user’ with specified ‘password’.
  • oracle://user:password@/sales — connecting to database SALES that is defined in TNSNAMES.ORA file.

Firebird

  • firebird://john:qqq@localhost/tests?Dialect=3&Connection lifetime=15&Pooling=true&MinPoolSize=0&MaxPoolSize=50&Packet Size=8192&ServerType=0 — connecting to database ‘tests’ as user ‘john’ with password ‘qqq’. Line breaks are added for formatting purposes.

SQLite

  • sqlite:///.\Tests.db3 — using database file Tests.db3 in current directory.

Default schema

By default DataObjects.Net maps all the entities to the default schema for the user specified in connection URL / string (authenticated user). If another schema must be used, you can specify it in DomainConfiguration.DefaultSchema or defaultScema attribute of domain element in App.config section.

Persistent types

Then you must register persistent types that will be used to build persistent model. Use Types collection and its Register method overloads to register single type, namespace or whole assembly. It is recommended to declare all persistent classes in separate namespace, e.g. MyCompany.MyProduct.Model and register all classes in this namespace.

domainConfig.Types.Register(
  typeof (Person).Assembly, typeof (Person).Namespace);
<domain
  name="Default"
  provider="sqlserver"
  connectionString="Data Source=localhost; Initial Catalog=MyDatabase; Integrated Security=True;"
  upgradeMode="Recreate">
    <types>
      <add assembly="MyCompany.MyProduct.Model" />
    </types>
</domain>

Persistent model can be also separated into several assemblies. In this case all assemblies that contain persistent model should be registered in domain types.

Upgrade mode

When domain is being built it analyzes existing database structure, and tries to upgrade it to actual model version. UpgradeMode property of DomainUpgradeMode type specifies how this upgrade should be performed. There are several available options:

  • Validate — Domain will check whether database schema is compatible with persistent model, anyway it will not be modified. If schema is incompatible, exception will be thrown.
  • Skip — No checks on database schema are performed. It is assumed to be compatible with current domain model.
  • Recreate — Database schema will be completely recreated, all existing data will be removed.
  • Perform — Storage upgrade will be performed. All missing database structures (columns, tables) will be added, excess ones will be removed. See database schema upgrade chapter for further details.
  • PerformSafely – The same as Perform mode, but any destructive operations (column or table removals, column type changes) require explicit declaration. If they are not explicitly declares exception is thrown before modifying database schema.
  • LegacyValidate and LegacySkip — legacy database compatibily mode with semantics similar to Validate and Skip respectively. See Legacy support section for more information.

Default upgrade mode value is PerformSafely.

domainConfig.UpgradeMode = DomainUpgradeMode.Recreate;

Naming conventions

NamingConvention property contains a set of rules for naming database structures such as tables, columns, indexes, etc. Several options are available.

NamingRules specifies underscore substitution for special symbols:

  • UnderscoreHyphens — replace all hypens with underscore symbol.
  • UnderscoreDots — replace all dots with underscore symbol.
  • Both options could be used.

By default dots and hypens are not underscored.

LetterCasePolicy specifies name transformation:

  • AsIs — no case transformations. This is default policy.
  • UpperCase — all identifiers are converted to upper case.
  • LowerCase — all identifiers are converted to lower case.

NamespacePolicy specifies namespace substitution for persistent entities:

  • Omit — omit namespace from table names. This is default option.
  • AsIs — namespaces are prepended to table names.
  • Hash — short namespace hashes are prepended to table names.
  • Synonymize — namespaces are replaced according to NamespaceSynonyms collection. You can provide your own prefix for each namespace. It would be prepended to table names. If namespace synonym is not specified for a particular namespace full name is used similar to AsIs mode.

Miscellaneous options

BuildInParallel property allows to enable parallel construction of a Domain. DataObjects.Net tries to minimize time for Domain.Build() by performing certain operations in separate thread. This option is enabled by default. If you for some reason don’t want separate thread to be used you could set this property to false.

ForcedServerVersion property allows to turn compatiblity with a particular version of database server. By default DataObjects.Net detects server version at start-up and uses recent features if they are available.

configuration.ForcedServerVersion = "9.0.0.0";

IncludeSqlInExceptions property allows to control message of exceptions that are thrown when error occurs when executing SQL query. By default query that cause the error is included into exception. You could disable such behavior by setting this property to false.

SchemaSyncExceptionFormat property allows to control message of exceptions that are thrown when database schema is not compatible with expect one. Two options are available:

  • Detailed — all relevant information is included. This is default option.
  • Brief — full schema difference is ommited. This enables behavior of DataObjects.Net 4.4 and earlier.

Loading domain configuration

Domain configuration can be also loaded from application configuration file. To do this you should add DataObjects.Net configuration section in your configuration file. One configuration section can contain several domain configurations, in the following example two domain configurations are declared (“stage” and “production”):

<configSections>
  <section
    name="Xtensive.Orm"
    type="Xtensive.Orm.Configuration.Elements.ConfigurationSection, Xtensive.Orm" />
</configSections>

<Xtensive.Orm>
  <domains>
    <domain name="stage"
            upgradeMode="Recreate"
            connectionUrl="sqlserver://localhost/MyStageDatabase">
      <types>
        <add assembly="MyAssembly" namespace="MyProduct.Model" />
      </types>
    </domain>
    <domain name="production"
            upgradeMode="Skip"
            connectionUrl="sqlserver://localhost/MyProductionDatabase">
      <types>
        <add assembly="MyAssembly" namespace="MyProduct.Model" />
      </types>
    </domain>
  </domains>
</Xtensive.Orm>

Domain configuration can be loaded from configuration file with the help of DomainConfiguration.Load() method:

//GetConfiguration() implementation may vary
System.Configuration.Configuration configuration = GetConfiguration();
var domainConfig = DomainConfiguration.Load(configuration, "MyDomain");

or if you need special section:

::
//GetConfiguration() implementation may vary System.Configuration.Configuration configuration = GetConfiguration(); var domainConfig = DomainConfiguration.Load(configuration, “customDomainsSection”, “MyDomain”);

Having that done you may change the configuration loaded setting any properties manually:

System.Configuration.Configuration configuration = GetConfiguration();
var domainConfig = DomainConfiguration.Load(configuration, "default");
domainConfig.UpgradeMode = DomainUpgradeMode.Recreate;
domainConfig.Types.Register(typeof (Person).Assembly, typeof (Person).Namespace);

Building domain

When domain configuration is ready, we can build domain using Domain.Build() static method:

var domain = Domain.Build(domainConfig);

or if you prefer asynchronous build:

var domain = Domain.BuildAsync(domainConfig);

During the build process DataObjects.Net makes the following steps:

  • Builds domain model that includes all types registered in DomainConfiguration.Types registry and additional custom definitions.
  • Builds storage schema based on persistent model.
  • Extracts actual storage schema from database.
  • Upgrades database schema according to the DomainUpgradeMode specified in DomainConfiguration.UpgradeMode.

As a result this method will return built and ready to use Domain instance. Its configuration is accessible in read-only mode from Domain.Configuration property.

Domain Events

Domain type exposes 2 events:

  • EventHandler Disposing
    Occurs when Domain is about to be disposed.
  • EventHandler<SessionEventArgs> SessionOpen
    Occurs when new Session is opened and activated.

Usually SessionOpen event is used to subscribe to various Session events of every newly opened Session instance:

Domain.SessionOpen += (source, args) => {
  args.Session.Events.TransactionOpened += TransactionOpened;
  args.Session.Events.TransactionCommitting += TransactionCommitting;
  ...
};